nestjs-commander, a nest command line tool

AI Response

If you want to integrate the 2dsphere index creation into your NestJS CLI workflow, the best approach is to create a custom NestJS command that can be executed using nest commands. This will follow the NestJS CLI style and allow better integration into your application lifecycle.


1. Create a Custom NestJS Command

NestJS allows creating command-line tools using nestjs-command or nestjs-console. We will use nestjs-command for this.

Install Required Dependencies

필요한 패키지를 설치하세요:

bash

npm install nest-commander

그리고 package.json에 스크립트를 추가하세요:

json

{
  "scripts": {
    "command": "nest start --entryFile=commands"
  }
}

2. Implement the Command in src/commands/create-index.command.ts

Create a new directory src/commands/ and add a file create-index.command.ts:

import { Injectable } from '@nestjs/common';
import { Command } from 'nestjs-command';
import { MongoClient } from 'mongodb';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class CreateIndexCommand {
  constructor(private readonly configService: ConfigService) {}

  @Command({
    command: 'db:create-index',
    describe: 'Create 2dsphere index on address.location.coordinates',
  })
  async createIndex() {
    const uri = this.configService.get<string>('DATABASE_URL');
    if (!uri) {
      console.error('❌ DATABASE_URL is not set.');
      return;
    }

    const client = new MongoClient(uri);
    try {
      await client.connect();
      const db = client.db(); // Get the default database from the connection string
      await db.collection('AcademyModel').createIndex({ 'address.location.coordinates': '2dsphere' });
      console.log('✅ 2dsphere index created on address.location.coordinates');
    } catch (error) {
      console.error('❌ Error creating 2dsphere index:', error);
    } finally {
      await client.close();
    }
  }
}

3. Register the Command in app.module.ts

Modify app.module.ts to include the command:

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { CommandModule } from 'nestjs-command';
import { CreateIndexCommand } from './commands/create-index.command';

@Module({
  imports: [
    ConfigModule.forRoot(), // Load environment variables
    CommandModule,
  ],
  providers: [CreateIndexCommand],
})
export class AppModule {}

4. Run the Command

Now you can run the custom command using the NestJS CLI:

pnpm run start db:create-index

# 아카데미 검색
npm run command search academy "XX아카데미"

# 코트 검색 (특정 아카데미 내에서)
npm run command search court "코트1" --academy "XX아카데미"

# 티켓플랜의 코트 업데이트 (추가)
npm run command update-ticket-plan-courts \
  --academy "XX아카데미" \
  --ticket-plan "프리미엄 플랜" \
  --courts "코트1,코트2,코트3"

# 티켓플랜의 코트 교체
npm run command update-ticket-plan-courts \
  --academy "XX아카데미" \
  --ticket-plan "프리미엄 플랜" \
  --courts "새코트1,새코트2" \
  --replace

5. Automate It in package.json

Modify your package.json script to run the Prisma migration and then create the index:

"scripts": {
  "prisma:push": "npx prisma db push && pnpm run start db:create-index"
}

Now, whenever you run:

pnpm run prisma:push

It will:

  1. Push the Prisma schema to MongoDB.
  2. Automatically create the 2dsphere index via the custom NestJS command.

Final Thoughts

Follows NestJS CLI conventions
Better error handling than raw shell scripts
Uses NestJS dependency injection (ConfigService for env variables)
Easier to extend for future commands

Would you like to add logging or extend this command further? 🚀

A. Chat GPT 4o

좋은 문제의식입니다. NestJS에서 개발자 편의 기능(DevOps/Debug API or CLI Tool) 을 제공하는 방식은 다음 두 가지 전략으로 나눌 수 있습니다:


✅ 선택지 요약

방법 장점 단점 추천 상황
1. @Controller('dev')로 관리자 전용 API 구성 Postman이나 Swagger로 바로 접근 가능 인증 없이 노출되면 위험 로컬/개발환경에서 빠른 조작
2. NestJS CLI Command (@Command()) 도입 CI/CD나 SSH 환경에서 스크립트 실행 편리 실행결과를 API로 받을 수 없음 프로덕션에도 안전하게 사용 가능
3. 둘 다 지원 (API + CLI) 유연성과 편의성 모두 확보 중복 코드 방지 필요 (Service 계층 분리 필수) 개발과 운영 모두 고려할 때

🛠 추천 구현 구조

// TicketPlanService.ts
async updateTicketPlanCourts(academyName: string, ticketPlanName: string, newCourtIds: string[]) {
  const academy = await this.academyRepo.findByName(academyName);
  const ticketPlan = await this.ticketPlanRepo.findByNameAndAcademy(ticketPlanName, academy._id);
  ticketPlan.courts = newCourtIds.map(id => new ObjectId(id));
  await this.ticketPlanRepo.update(ticketPlan._id, ticketPlan);
}

1. 개발자 전용 API (예:dev/ticket-plan/update-courts)

// dev.controller.ts
@Controller('dev/ticket-plan')
export class DevTicketPlanController {
  constructor(private readonly ticketPlanService: TicketPlanService) {}

  @Post('update-courts')
  @UseGuards(AdminOnlyGuard) // 혹은 환경별 접근제한
  async updateCourts(@Body() dto: UpdateTicketPlanCourtsDto) {
    await this.ticketPlanService.updateTicketPlanCourts(dto.academyName, dto.ticketPlanName, dto.newCourtIds);
    return { success: true };
  }
}

export class UpdateTicketPlanCourtsDto {
  @IsString() academyName: string;
  @IsString() ticketPlanName: string;
  @IsArray() @IsMongoId({ each: true }) newCourtIds: string[];
}

2. NestJS CLI 명령어 지원 (Command Mode)

NestJS는 @nestjs/cli로 직접 명령어를 추가하긴 어렵지만, nestjs-command나 @commander-js/extra-typings을 사용하는 방식이 있습니다.

// ticket-plan.command.ts
import { Command, CommandRunner, Option } from 'nest-commander';

@Command({ name: 'ticket-plan:update-courts', description: 'Update courts for a ticket plan' })
export class UpdateCourtsCommand extends CommandRunner {
  constructor(private readonly ticketPlanService: TicketPlanService) {
    super();
  }

  async run(passedParams: string[], options?: Record<string, any>): Promise<void> {
    const [academyName, ticketPlanName, ...courtIds] = passedParams;
    await this.ticketPlanService.updateTicketPlanCourts(academyName, ticketPlanName, courtIds);
    console.log(`Updated ticket plan courts for ${ticketPlanName}`);
  }
}
# 실행 예시
$ nestjs ticket-plan:update-courts "XX 아카데미" "1개월권" 60b... 61c... 62f...

🔐 보안 팁


📌 확장 고려사항


✅ 결론

NestJS 프로젝트에선 서비스 계층을 기준으로 명령형 로직을 묶고, 그 위에 @Controller와 @Command를 모두 붙여주는 방식이 가장 유연합니다.

클린하고 재사용 가능한 로직 구조를 유지하면서도, 빠른 테스트와 조작이 가능합니다.